/*
 * pixiv_down - CLI-based downloading tool for https://www.pixiv.net.
 * Copyright (C) 2025  Mio
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

/// This module handles the loading and saving of the error cache.
module pd.error_cache;

import std.container.rbtree;
import std.exception;
import std.path;
import std.stdio;

import mlib.directories;

struct ErrorCache
{
    RedBlackTree!string artworks;
    RedBlackTree!string novels;
    bool successfullyLoaded;
}

/// Loads all cached errors from disk.
ErrorCache loadErrorCache() @trusted
{
    import std.string: split;

    const ProjectDirectories dirs = getProjectDirectories(null, "YumeNeru Software", "pixiv_down");

    auto cache = ErrorCache(new RedBlackTree!string(), new RedBlackTree!string());
    File errorFile;

    try {
        errorFile = File(buildPath(dirs.stateDir, "errors"), "r");
    } catch (ErrnoException e) {
        cache.successfullyLoaded = false;
        return cache;
    }

    foreach(const ref line; errorFile.byLineCopy) {
        if (line.length == 0) continue;

        string[] parts = line.split("\t");
        if (parts.length < 2) continue;

        switch(parts[0])
        {
        case "artwork":
            cache.artworks.insert(cast(string)parts[1]);
            break;
        case "novel":
            cache.novels.insert(cast(string)parts[1]);
            break;
        default:
            break;
        }

    }

    cache.successfullyLoaded = true;

    return cache;
}


/// Save all error items to disk.
void save(const ref ErrorCache cache) @trusted
{
    import std.algorithm.iteration: each;

    const ProjectDirectories dirs = getProjectDirectories(null, "YumeNeru Software", "pixiv_down");

    File errorFile;

    try {
        errorFile = File(buildPath(dirs.stateDir, "errors"), "w");
    } catch (ErrnoException e) {
        // TODO: handle better.
        stderr.writefln("Failed to save errors file: %s", e.msg);
        return;
    }

    cache.artworks.each!(artwork => errorFile.writefln("artwork\t%s", artwork));
    cache.novels.each!(novel => errorFile.writefln("novel\t%s", novel));
}

version(OSX) {}
version(Posix):

@safe unittest
{
    import std.conv: to;
	import std.file: exists, mkdirRecurse;
    import std.process: environment;
    import std.algorithm.comparison: equal;

	environment["XDG_STATE_HOME"] = "/tmp/";
	auto projectDirs = getProjectDirectories(null, "YumeNeru Software", "pixiv_down");

	mkdirRecurse(projectDirs.stateDir);

	ErrorCache cache = loadErrorCache();

    for (size_t i = 0; i < 10; ++i) {
        cache.artworks.insert(to!string(i));
        cache.novels.insert(to!string(9 - i)); // test sorting
	}

	save(cache);
    assert(exists(buildPath(projectDirs.stateDir, "errors")),
           "errors file does not exist after saving");

    ErrorCache cache2 = loadErrorCache();
    const auto exp =  ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
    assert(equal(cache2.artworks[], exp),
           "Expected " ~ to!string(exp) ~ " got " ~ to!string(cache2.artworks[]));
    assert(equal(cache2.novels[], exp),
           "Expected " ~ to!string(exp) ~ " got " ~ to!string(cache2.novels[]));
}
